#include <buola/chart/plots/clineplot.h>

#include <buola/image/cgc.h>
#include <buola/chart/cxychart.h>
#include <buola/chart/cxaxis.h>
#include <buola/chart/cyaxis.h>

namespace buola { namespace chart {

CLinePlot::CLinePlot()
    :   mLineColor(std::wstring(L"orangered")),
        mLineWidth(2),
        mPointColor(std::wstring(L"orangered")),
        mPointDiameter(5),
        mPointShape(POINT),
        mFillColor(std::wstring(L"orangered"),0.3)
{
    mStyle=CPlot::LINE;
}

CLinePlot::~CLinePlot()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CLinePlot::SetSeries(PSeries pSeries)
{
    mSeries=pSeries;
}

void CLinePlot::SetColor(const img::CColor &pColor)
{
    mLineColor=pColor;
    mPointColor=pColor;
    mFillColor=pColor;
    mFillColor.a=0.3;
}

void CLinePlot::SetLineWidth(double pWidth)
{
    mLineWidth=pWidth;
}

void CLinePlot::SetLineColor(const img::CColor &pColor)
{
    mLineColor=pColor;
}

void CLinePlot::SetPointDiameter(double pDiameter)
{
    mPointDiameter=pDiameter;
}

void CLinePlot::SetPointShape(ePointShape pShape)
{
    mPointShape=pShape;
}

void CLinePlot::SetPointColor(const img::CColor &pColor)
{
    mPointColor=pColor;
}

void CLinePlot::SetPointFillColor(const img::CColor &pColor)
{
    mPointFillColor=pColor;
}

void CLinePlot::SetFillColor(const img::CColor &pColor)
{
    mFillColor=pColor;
}

void CLinePlot::SetName(const std::wstring &pName)
{
    if(mSeries)
        mSeries->mName=pName;
}

void CLinePlot::ClearDontSkipList()
{
    mDontSkipList.clear();
}

void CLinePlot::AddToDontSkipList(int i)
{
    mDontSkipList.insert(i);
}

void CLinePlot::Draw(CXYChartContext &pC)
{
    if(!mSeries->size()) return;

    if(!(mStyle&DISABLED))
    {
        //first, decide which points will actually be drawn
        mPoints.clear();

        bool lStarted=false;
        CPoint_d lLast;
        for(int i=0;i<mSeries->size();i++)
        {
            CPoint_d lThis(pC.mXAxis->Map(mSeries->GetX(i)),
                         pC.mYAxis->Map(mSeries->GetY(i)));

            if(!lStarted)   //we don't have any point yet
            {
                if(lThis.x<pC.mRect.l)  //outside the window, ignore it
                {
                    lLast=lThis;
                    continue;
                }
                else    //this is the first point
                {
                    if(i>0) //it's not the first, so take previous one
                    {
                        SPoint lPoint;
                        lPoint.i=i-1;
                        lPoint.mPoint=lLast;
                        lPoint.mData={mSeries->GetX(i-1),mSeries->GetY(i-1)};
                        mPoints.push_back(lPoint);
                    }

                    lStarted=true;
                }
            }

            if(i>0&&lThis.x-lLast.x<2&&i<mSeries->size()-1) //skip it
                continue;

            SPoint lPoint;
            lPoint.i=i;
            lPoint.mPoint=lThis;
            lPoint.mData={mSeries->GetX(i),mSeries->GetY(i)};
            mPoints.push_back(lPoint);

            if(lThis.x>pC.mRect.r)
                break;

            lLast=lThis;
        }

        if(mPoints.size()<mSeries->size()) //some were skipped
        {
            typedef std::set<int>::iterator tSetIterator;
            typedef std::vector<SPoint>::iterator tVecIterator;
            for(tSetIterator i=mDontSkipList.begin();i!=mDontSkipList.end();++i)
            {
                bool lFound=false;
                tVecIterator p;
                for(p=mPoints.begin();p!=mPoints.end();++p)
                {
                    if(p->i==*i)
                    {
                        lFound=true;
                        break;
                    }
                    if(p->i>*i)
                    {
                        break;
                    }
                }

                if(!lFound)
                {
                    SPoint lPoint;
                    lPoint.i=*i;
                    lPoint.mData={mSeries->GetX(*i),mSeries->GetY(*i)};
                    lPoint.mPoint={pC.mXAxis->Map(lPoint.mData.x),pC.mYAxis->Map(lPoint.mData.y)};
                    mPoints.insert(p,lPoint);
                }
            }
        }

        DrawComplements(pC);

        if(mStyle&(CLinePlot::LINE|CLinePlot::FILL))
        {
            if(!mPoints.empty())
            {
                pC.mGC->Path().MoveTo(mPoints[0].mPoint);

                for(int i=1;i<mPoints.size();i++)
                {
                    pC.mGC->Path().LineTo(mPoints[i].mPoint);
                }
            }

            if(mStyle&CLinePlot::LINE)
            {
                if(mHighlighted)
                {
                    img::CColor lDimColor(mLineColor);
                    lDimColor.a/=3;
                    pC.mGC->SetSource(lDimColor);
                    pC.mGC->SetLineJoin(img::ELineJoin::ROUND);
                    pC.mGC->SetLineWidth(mLineWidth*5);
                    pC.mGC->StrokePath(true);
                    pC.mGC->SetLineJoin(img::ELineJoin::MITER);
                }

                pC.mGC->SetSource(mLineColor);
                pC.mGC->SetLineWidth(mLineWidth);
                pC.mGC->StrokePath(mStyle&CLinePlot::FILL);
            }
            if(mStyle&CLinePlot::FILL)
            {
                pC.mGC->Path().LineTo(CPoint_d(mPoints[0].mPoint.x,pC.mRect.b));
                pC.mGC->Path().LineTo(CPoint_d(mPoints[mPoints.size()-1].mPoint.x,pC.mRect.b));
                pC.mGC->Path().Close();
                pC.mGC->SetSource(mFillColor);
                pC.mGC->FillPath();
            }
        }
        if(mStyle&CLinePlot::STEM)
        {
            pC.mGC->SetSource(mLineColor);
            pC.mGC->SetLineWidth(mLineWidth);

            double lYBase=pC.mYAxis->Map(0.0);

            for(int j=0;j<mPoints.size();j++)
            {
                pC.mGC->Path().MoveTo(mPoints[j].mPoint);
                pC.mGC->Path().LineTo(CPoint_d(mPoints[j].mPoint.x,lYBase));
                pC.mGC->StrokePath();
            }
        }
        if(mStyle&CLinePlot::POINTS)
        {
            SPointDrawer lDrawer(pC.mGC,mPointShape,mStyle);
            lDrawer.SetDiameter(mPointDiameter);
            lDrawer.SetColor(mPointColor);
            lDrawer.SetFillColor(mPointFillColor);
            for(int j=0;j<mPoints.size();j++)
            {
                lDrawer.Draw(mPoints[j].mPoint);
            }
        }
    }


    CLegend::SItem lItem;
    lItem.mText=mSeries->mName;
    lItem.mColor=mLineColor;
    lItem.mPlot=this;
    lItem.mStyle=mStyle;
    pC.mLegend->AddItem(lItem);
}

void CLinePlot::DrawHover(CXYChartContext &pC,const CPoint_d &pPos)
{
    CPoint_d lNearest=GetNearestPoint(pPos,pC.mXAxis,pC.mYAxis);

    SPointDrawer lDrawer(pC.mGC,CIRCLE,mStyle);
    lDrawer.SetDiameter(mPointDiameter);
    lDrawer.SetColor(mPointColor);
    lDrawer.SetFillColor(mPointFillColor);

    lDrawer.Draw(CPoint_d(pC.mXAxis->Map(lNearest.x),
                         pC.mYAxis->Map(lNearest.y)));
}

double CLinePlot::GetDist(const CPoint_d &pPos,CXAxis *pXAxis,CYAxis *pYAxis)
{
    if(!mPoints.size()) return INFINITY;

    int lPos;

    for(lPos=0;lPos<mPoints.size();lPos++)
    {
        if(mPoints[lPos].mPoint.x>pPos.x)
            break;
    }

    if(lPos==0)
    {
        return dist(pPos,mPoints[0].mPoint);
    }
    else if(lPos==mPoints.size())
    {
        return dist(pPos,mPoints[lPos-1].mPoint);
    }
    else
    {
        return min(dist(pPos,mPoints[lPos-1].mPoint),dist(pPos,mPoints[lPos].mPoint));
    }
}

CPoint_d CLinePlot::GetNearestPoint(const CPoint_d &pPos,CXAxis *pXAxis,
                                            CYAxis *pYAxis)
{
    int lPos;

    for(lPos=0;lPos<mPoints.size();lPos++)
    {
        if(mPoints[lPos].mPoint.x>pPos.x)
            break;
    }

    if(lPos==0)
    {
        return mPoints[0].mData;
    }
    else if(lPos==mSeries->size())
    {
        return mPoints[lPos-1].mData;
    }
    else
    {
        if(pPos.x-mPoints[lPos-1].mPoint.x<mPoints[lPos].mPoint.x-pPos.x)
        {
            return mPoints[lPos-1].mData;
        }
        else
        {
            return mPoints[lPos].mData;
        }
    }
}

/*namespace chart*/ } /*namespace buola*/ }
